package com.chenfan.mcn.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chenfan.ccp.common.result.R;
import com.chenfan.common.exception.BusinessException;
import com.chenfan.common.vo.UserVO;
import com.chenfan.mcn.config.UserVoContextHolder;
import com.chenfan.mcn.constant.MsgCenterTemplateCst;
import com.chenfan.mcn.dao.ApprovalFlowMapper;
import com.chenfan.mcn.enums.ApprovalEnum;
import com.chenfan.mcn.exception.McnErrorCode;
import com.chenfan.common.extension.spring.TransactionUtils;
import com.chenfan.common.extension.util.SmartCompletableFuture;
import com.chenfan.mcn.model.ApprovalFlow;
import com.chenfan.mcn.model.dto.ApprovalFlowDTO;
import com.chenfan.mcn.service.ApprovalFlowService;
import com.chenfan.mcn.utils.*;
import com.chenfan.notify.MessageClient;
import com.chenfan.notify.vo.MessageDetailVo;
import com.chenfan.notify.vo.MessagePlanVo;
import com.chenfan.process.client.ProcessClient;
import com.chenfan.process.dto.*;
import com.chenfan.process.vo.ApprovalNodeVO;
import com.chenfan.process.vo.ApprovalOperationVO;
import com.chenfan.process.vo.ApprovalVo;
import com.chenfan.process.vo.NextNotifyVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

/**
 * <p>
 * 业务工作流关联表 服务实现类
 * </p>
 *
 * @author lizhejin
 * @since 2021-01-26
 */
@Slf4j
@Service
public class ApprovalFlowServiceImpl extends ServiceImpl<ApprovalFlowMapper, ApprovalFlow> implements ApprovalFlowService {

    @Autowired
    private ProcessClient processClient;

    @Autowired
    private MessageClient messageClient;

    private final BusinessException flowSubmitterNullExp = new BusinessException(McnErrorCode.FLOW_SUBMITTER_NULL);

    @Override
    public void startProcess(Long srcId, String businessCode, ApprovalEnum approvalEnum, String param) {
        this.startProcess(srcId, srcId, businessCode, approvalEnum, param, null);
    }

    @Override
    public void startProcess(Long srcId, String businessCode, ApprovalEnum approvalEnum, String param, String checkFile) {
        this.startProcess(srcId, srcId, businessCode, approvalEnum, param, checkFile);
    }

    @Override
    public void startProcess(Long srcId, Long businessId, String businessCode, ApprovalEnum approvalEnum, String param, String checkField) {
        Long userId = Optional.ofNullable(UserVoContextHolder.getUserId()).orElseThrow(() -> flowSubmitterNullExp);
        String userName = Optional.ofNullable(UserVoContextHolder.getUserName()).orElseThrow(() -> flowSubmitterNullExp);
        BusinessAssert.notNull(approvalEnum, McnErrorCode.FLOW_NOT_FOUND);
        Long processId = approvalEnum.getProcessId();
        ApprovalFlow approvalFlow = baseMapper.selectOne(Wrappers.<ApprovalFlow>lambdaQuery().eq(ApprovalFlow::getSrcId, srcId).eq(ApprovalFlow::getActiveStatus, true).eq(ApprovalFlow::getProcessId, processId));
        if (Objects.nonNull(approvalFlow)) {
            throw new BusinessException(McnErrorCode.FLOW_SAVE_DUPLICATE);
        }
        Long flowId;
        JSONObject businessParam = new JSONObject();
        //businessParam.put("url", approvalEnum.getUrl());
        businessParam.put("createName", userName);
        businessParam.put("submitName", userName);
        //businessParam.put("businessId", srcId);
        businessParam.put("name", approvalEnum.getProcessName());
        //businessParam.put("code", businessCode);
        if(StringUtils.isNotBlank(checkField)) {
            flowId = processClient.startProcess(processId, userId, userName, param, checkField, businessId, businessCode, businessParam.toJSONString()).getObj();
        } else {
            flowId = processClient.startProcess(processId, userId, userName, param, businessId, businessCode, businessParam.toJSONString()).getObj();
        }
        if (flowId == null || flowId.longValue() <= 0) {
            throw new BusinessException(McnErrorCode.FLOW_SAVE_ERROR);
        }
        ApprovalFlow insertApprovalFlow = new ApprovalFlow()
                .setSrcId(srcId)
                .setProcessId(processId)
                .setApprovalId(flowId)
                .setUserId(userId)
                .setUserName(userName)
                .setDepartmentId(UserVoContextHolder.getDepartmentId())
                .setCompanyId(UserVoContextHolder.getCompanyId())
                .setTenantId(UserVoContextHolder.getTenantId());
        baseMapper.insert(insertApprovalFlow);
    }

    @Override
    public List<Long> getSrcList(List<Long> flowUserIds, Long processId) {
        ApprovalStatusDTO approvalStatus = new ApprovalStatusDTO();
        approvalStatus.setApprovalTemplateId(processId.toString());
        approvalStatus.setUserIdList(flowUserIds);
        approvalStatus.setPageNum(1);
        approvalStatus.setPageSize(Integer.MAX_VALUE);
        R<List<ApprovalVo>> res = processClient.listInstanceStatusList(approvalStatus);
        if (Objects.isNull(res) || res.getCode() != 0) {
            throw new BusinessException(McnErrorCode.FLOW_ERROR);
        }
        return CommonUtil.defaultList(res.getObj()).stream().map(ApprovalVo::getBusinessId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
    }

    @Override
    public List<Long> getSrcList(List<Long> flowUserIds, List<Long> processIds) {
        if (CollectionUtils.isEmpty(flowUserIds) || CollectionUtils.isEmpty(processIds)) {
            return null;
        }
        if(processIds.size() == 1) {
            return this.getSrcList(flowUserIds, processIds.get(0));
        }
        List<Long> srcIdList = new ArrayList<>();
        List<CompletableFuture> completableFutureList = new ArrayList<>(processIds.size());
        for (Long processId : processIds) {
            completableFutureList.add(SmartCompletableFuture.runAsync(() -> srcIdList.addAll(this.getSrcList(flowUserIds, processId)) ));
        }
        SmartCompletableFuture.allOf(completableFutureList.toArray(new CompletableFuture[]{})).join();
        return srcIdList;
    }

    @Override
    public void revokeProcess(Long srcId, Long processId) {
        UserVO userVO = UserVoContextHolder.getUserVo();
        ApprovalFlow approvalFlow = baseMapper.selectOne(Wrappers.<ApprovalFlow>lambdaQuery().eq(ApprovalFlow::getSrcId, srcId).eq(ApprovalFlow::getProcessId, processId).eq(ApprovalFlow::getActiveStatus, true));
        if (Objects.isNull(approvalFlow)) {
            return;
        }
        Long response = processClient.revokeApproval(approvalFlow.getApprovalId(), userVO.getUserId(), userVO.getRealName()).getObj();
        if (response <= 0) {
            throw new BusinessException(McnErrorCode.FLOW_SAVE_ERROR);
        }
        approvalFlow.setActiveStatus(false);
        baseMapper.updateById(approvalFlow);
    }

    @Override
    public JSONObject getFlowStatus(List<Long> flowIdList) {
        if (CollectionUtils.isNotEmpty(flowIdList)) {
            flowIdList.removeAll(Collections.singleton(null));
            R<Map<Long, String>> res = processClient.approvalToMap(flowIdList);
            if (Objects.isNull(res) || res.getCode() != 0) {
                throw new BusinessException(McnErrorCode.FLOW_ERROR);
            }
            if (Objects.nonNull(res.getObj())) {
                return JSONObject.parseObject(JSONObject.toJSONString(res.getObj()));
            }
        }
        return new JSONObject(0);
    }

    @Override
    public void sendNotify(ApprovalFlowDTO approvalFlowDTO, Long srcId, String srcCode, ApprovalEnum approvalEnum, Boolean status, Long targetUserId, String targetUserName) {
        TransactionUtils.afterCommit(() -> {
            if(Objects.nonNull(status)) {
                MessagePlanVo planVo = new MessagePlanVo();
                planVo.setTenantId(ObjectUtils.defaultIfNull(approvalFlowDTO.getTenantId(), 1L));
                planVo.setMessageKey(CommonUtil.getMessageKey(srcCode));
                planVo.setJumpUrlType(approvalEnum.getType());
                planVo.setTemplate(status.booleanValue() ? MsgCenterTemplateCst.APPROVAL_SUCCESS : MsgCenterTemplateCst.APPROVAL_FAILURE);
                MessageDetailVo messageDetailVo = new MessageDetailVo();
                messageDetailVo.setUserId(targetUserId);
                Map<String, String> externParams = new HashMap<>(4);
                externParams.put("url", approvalEnum.getUrl());
                externParams.put("businessId", srcId.toString());
                externParams.put("name", approvalEnum.getProcessName());
                externParams.put("code", srcCode);
                messageDetailVo.setExternParams(externParams);
                planVo.setDetails(Arrays.asList(messageDetailVo));
                messageClient.sendMessage(planVo);
            }
            //抄送CC
            List<ApprovalCallBack.CarbonCopyDTO> carbonCopyList = approvalFlowDTO.getCarbonCopyList();
            if (CollectionUtils.isNotEmpty(carbonCopyList)) {
                MessagePlanVo planVo = new MessagePlanVo();
                planVo.setTenantId(ObjectUtils.defaultIfNull(approvalFlowDTO.getTenantId(), 1L));
                planVo.setMessageKey(CommonUtil.getMessageKey(srcCode));
                planVo.setJumpUrlType(approvalEnum.getType());
                planVo.setTemplate(MsgCenterTemplateCst.APPROVAL_CC);
                List<MessageDetailVo> detailVos = new ArrayList<>(carbonCopyList.size());
                carbonCopyList.forEach(cc -> {
                    MessageDetailVo messageDetailVo = new MessageDetailVo();
                    messageDetailVo.setUserId(cc.getUserId());
                    Map<String, String> ccExternParams = new HashMap<>(8);
                    ccExternParams.put("url", approvalEnum.getUrl());
                    ccExternParams.put("submitName", approvalFlowDTO.getUserName());
                    ccExternParams.put("businessId", srcId.toString());
                    ccExternParams.put("name", approvalEnum.getProcessName());
                    ccExternParams.put("code", srcCode);
                    messageDetailVo.setExternParams(ccExternParams);
                    detailVos.add(messageDetailVo);
                });
                planVo.setDetails(detailVos);
                messageClient.sendMessage(planVo);
            }
        });
    }


    @Override
    @Async
    public void sendBusinessNotify(Long tenantId, Long srcId, String srcCode, String title, ApprovalEnum approvalEnum, boolean status, Long targetUserId, String targetUserName, Integer messageProgress) {
        MessagePlanVo planVo = new MessagePlanVo();
        planVo.setTenantId(tenantId);
        planVo.setMessageKey(CommonUtil.getMessageKey(srcCode));
        planVo.setJumpUrlType(approvalEnum.getType());
        planVo.setTemplate(status ? MsgCenterTemplateCst.BUSINESS_COMMISSION_CONFIRM : MsgCenterTemplateCst.BUSINESS_COMMISSION_SEND);
        MessageDetailVo messageDetailVo = new MessageDetailVo();
        if(status) {
            messageDetailVo.setUserId(targetUserId);
        }
        Map<String, String> externParams = new HashMap<>(8);
        externParams.put("url", approvalEnum.getUrl());
        externParams.put("businessId", srcId.toString());
        externParams.put("title", title);
        if(status) {
            externParams.put("userId", targetUserId.toString());
        }
        externParams.put("messageProgress", messageProgress.toString());
        messageDetailVo.setExternParams(externParams);
        planVo.setDetails(Arrays.asList(messageDetailVo));
        messageClient.sendMessage(planVo);
    }

    @Override
    @Async
    public void sendAeNotify(Long tenantId, Long srcId, String srcCode, String quarterCode, ApprovalEnum approvalEnum, boolean status, Long targetUserId, String targetUserName, Integer messageProgress) {
        MessagePlanVo planVo = new MessagePlanVo();
        planVo.setTenantId(tenantId);
        planVo.setMessageKey(CommonUtil.getMessageKey(srcCode));
        planVo.setJumpUrlType(approvalEnum.getType());

        planVo.setTemplate(status ? MsgCenterTemplateCst.AE_COMMISSION_CONFIRM : MsgCenterTemplateCst.AE_COMMISSION_SEND);
        MessageDetailVo messageDetailVo = new MessageDetailVo();
        if(status) {
            messageDetailVo.setUserId(targetUserId);
        }
        Map<String, String> externParams = new HashMap<>(8);
        externParams.put("quarterCode", quarterCode);
        externParams.put("url", approvalEnum.getUrl());
        externParams.put("businessId", srcId.toString());
        if(status) {
            externParams.put("userId", targetUserId.toString());
        }
        externParams.put("messageProgress", messageProgress.toString());
        messageDetailVo.setExternParams(externParams);
        planVo.setDetails(Arrays.asList(messageDetailVo));
        messageClient.sendMessage(planVo);
    }


    @Override
    public List<Long> getFlowIds(Long srcId, Long processId) {
        List<ApprovalFlow> approvalFlows = baseMapper.selectList(Wrappers.lambdaQuery(ApprovalFlow.class)
                .eq(ApprovalFlow::getActiveStatus, Boolean.TRUE)
                .eq(ApprovalFlow::getProcessId, processId)
                .eq(ApprovalFlow::getSrcId, srcId));
        List<Long> flowIds = new ArrayList<>();
        if (CollectionUtil.isNotEmpty(approvalFlows)) {
            flowIds = approvalFlows.stream().sorted(Comparator.comparing(ApprovalFlow::getCreateDate).reversed())
                    .map(ApprovalFlow::getApprovalId).collect(Collectors.toList());
        }
        return flowIds;
    }

    @Override
    public Long getFlowId(Long srcId, Long processId) {
        return baseMapper.getFlowId(srcId, processId);
    }

    @Override
    public String getStringFlowIds(Long srcId, Long processId) {
        return baseMapper.getFlowIds(srcId, processId);
    }

    @Override
    public Map<Long, Long> getNewestFlowIds(List<Long> srcIds, List<Long> processIds) {
        if (CollectionUtils.isEmpty(srcIds) || CollectionUtils.isEmpty(processIds)) {
            return new HashMap<>(0);
        }
        List<ApprovalFlow> dataList = this.baseMapper.getNewestFlowIds(srcIds, processIds);
        if (CollectionUtils.isEmpty(dataList)) {
            return new HashMap<>(0);
        }
        return dataList.stream().collect(Collectors.toMap(ApprovalFlow::getSrcId, ApprovalFlow::getApprovalId));
    }

    @Override
    public Map<Long, ApprovalNodeVO> getCurrentApprovalNodes(List<Long> srcIds, List<Long> processIds) {
        Map<Long, Long> flowIdMap = this.getNewestFlowIds(srcIds, processIds);
        if(MapUtils.isEmpty(flowIdMap)) {
            return new HashMap<>(0);
        }
        List<ApprovalNodeVO> approvalNodeVOList = processClient.listApprovalNode(ApprovalMapDTO.builder().approvalIdList(flowIdMap.values()).build()).getObj();
        if (CollectionUtils.isNotEmpty(approvalNodeVOList)) {
            Map<Long, ApprovalNodeVO> nodeMap = new HashMap<>(approvalNodeVOList.size());
            for (ApprovalNodeVO flow : approvalNodeVOList) {
                for(Map.Entry<Long, Long> entry : flowIdMap.entrySet()) {
                    if(flow.getApprovalId().equals(entry.getValue())) {
                        nodeMap.put(entry.getKey(), flow);
                        break;
                    }
                }
            }
            return nodeMap;
        }
        return new HashMap<>(0);
    }


    /**
     * 获取当前审批流信息
     *
     * @param approvalMap
     * @return
     */
    @Override
    public Map<Long, String> approvalToMap(@RequestBody ApprovalMapDTO approvalMap) {
        return RpcUtil.getObjException(processClient.approvalToMap(approvalMap), "审批流异常");
    }

    /**
     * 审批流程
     *
     * @param approvalId
     * @param parameter
     */
    @Override
    public ApprovalOperationVO approvalChange(Long approvalId, String parameter, Boolean approvalFlag, String remark) {
        UserVO userVo = UserVoContextHolder.getUserVo();
        ApprovalOperationDTO approvalOperationDTO = new ApprovalOperationDTO();
        approvalOperationDTO.setApprovalId(approvalId);
        approvalOperationDTO.setUserId(userVo.getUserId());
        approvalOperationDTO.setUserName(userVo.getRealName());
        approvalOperationDTO.setApprovalFlag(approvalFlag);
        approvalOperationDTO.setParameter(parameter);
        approvalOperationDTO.setRemark(remark);
        List<ApprovalOperationDTO> approvalList = Arrays.asList(approvalOperationDTO);
        R<List<ApprovalOperationVO>> result = processClient.approvalChange(approvalList);
        List<ApprovalOperationVO> approvalOperationVOList = result.getObj();
        if (result.getCode() == McnErrorCode.FLOW_LACK_CONFIGURATION_ERROR.code()) {
            throw new BusinessException(McnErrorCode.FLOW_LACK_CONFIGURATION_ERROR);
        }
        if (CollectionUtils.isEmpty(approvalOperationVOList)) {
            return null;
        }
        return approvalOperationVOList.get(0);
    }

    @Override
    public NextNotifyVo approvalChangeNext(Long approvalId, String parameter, Boolean approvalFlag, String remark) {
        UserVO userVo = UserVoContextHolder.getUserVo();
        ApprovalDTO approvalOperationDTO = ApprovalDTO.builder()
                .useId(userVo.getUserId())
                .userName(userVo.getRealName())
                .param(ApprovalDTO.Params.builder()
                        .approvalFlag(approvalFlag)
                        .parameter(parameter)
                        .remark(remark)
                        .build()
                ).build();
        R<NextNotifyVo> result = processClient.approvalChangeAndReturnNextOne(approvalId, approvalOperationDTO);
        NextNotifyVo nextNotifyVo = result.getObj();
        if (result.getCode() == McnErrorCode.FLOW_LACK_CONFIGURATION_ERROR.code()) {
            throw new BusinessException(McnErrorCode.FLOW_LACK_CONFIGURATION_ERROR);
        }
        return nextNotifyVo;
    }
}
